package com.tebiecloud.uaa.admin.server.controller;

import com.google.common.collect.Maps;
import com.tebiecloud.ali.client.constants.SmsTypeEnum;
import com.tebiecloud.base.client.constants.BaseConstants;
import com.tebiecloud.base.client.model.entity.BaseAccount;
import com.tebiecloud.base.client.model.entity.BaseUser;
import com.tebiecloud.common.constants.ResultEnum;
import com.tebiecloud.common.model.ResultBody;
import com.tebiecloud.common.properties.CommonProperties;
import com.tebiecloud.common.security.SecurityConstants;
import com.tebiecloud.common.security.SecurityHelper;
import com.tebiecloud.common.security.SecurityUserDetails;
import com.tebiecloud.common.security.oauth2.client.OAuth2ClientDetails;
import com.tebiecloud.common.security.oauth2.client.OAuth2ClientProperties;
import com.tebiecloud.common.utils.*;
import com.tebiecloud.mpopen.client.model.mpopen.AccessToken;
import com.tebiecloud.mpweixin.client.model.entity.MpminiAccountRef;
import com.tebiecloud.mpweixin.client.model.entity.MpminiUser;
import com.tebiecloud.mpweixin.client.model.entity.MpweixinUser;
import com.tebiecloud.uaa.admin.server.constants.UaaAdminConstants;
import com.tebiecloud.uaa.admin.server.service.feign.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerEndpointsConfiguration;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.web.bind.annotation.*;

import java.security.Principal;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

/**
 * 用户认证中心
 * @Author: tebie
 * @Date: 2019-08-10 13:53
 */
@Slf4j
@RestController
public class LoginController {

    @Autowired
    private CommonProperties commonProperties;
    @Autowired
    private BCryptPasswordEncoder passwordEncoder;
    @Autowired
    private OAuth2ClientProperties oAuth2ClientProperties;
    @Autowired
    private TokenStore tokenStore;
    @Autowired
    private AuthorizationServerEndpointsConfiguration endpoints;
    @Autowired
    private RedisUtils redisUtils;
    @Autowired
    private AliSmsServiceClient aliSmsServiceClient;
    @Autowired
    private MpminiAccountRefServiceClient mpminiAccountRefServiceClient;
    @Autowired
    private MpopenApiServiceClient mpopenApiServiceClient;
    @Autowired
    private MpweixinApiServiceClient mpweixinApiServiceClient;
    @Autowired
    private BaseUserServiceClient baseUserServiceClient;
    @Autowired
    private MpminiApiServiceClient mpminiApiServiceClient;
    @Autowired
    private StoreStaffServiceClient storeStaffServiceClient;

    /**
     * 获取当前登录用户信息
     * @return
     */
    @GetMapping("/current/user")
    public ResultBody getUserProfile() {
        return ResultBodyUtils.ok(SecurityHelper.getUser());
    }

    /**
     * 获取当前登录用户信息-SSO单点登录
     * @param principal
     * @return
     */
    @GetMapping("/current/user/sso")
    public Principal principal(Principal principal) {
        return principal;
    }

    @PostMapping("/login/token2")
    public Object getLoginToken2(
            @RequestParam(value = "nickname", required = false) String nickname,
            @RequestParam(value = "headimgurl", required = false) String headimgurl,
            @RequestParam(value = "username", required = false) String username,
            @RequestParam(value = "password", required = false) String password,
            @RequestParam(value = "type", required = false, defaultValue = "username") String type) throws Exception {
        return getLoginToken(nickname,headimgurl,username, password, type);
    }

    /**
     * 获取用户访问令牌
     * 基于oauth2密码模式登录
     * @param username 登录名
     * @param password 登录密码
     * @param type 登录类型，username:系统用户名、email：邮箱、mobile：手机号、qq：QQ号、mpweixin：微信号、weibo：微博、mp：微信公众号、miniprogram：微信小程序
     * @return
     */
    @PostMapping("/login/token")
    public Object getLoginToken(
            @RequestParam(value = "nickname", required = false) String nickname,
            @RequestParam(value = "headimgurl", required = false) String headimgurl,
            @RequestParam(value = "username", required = false) String username,
            @RequestParam(value = "password", required = false) String password,
            @RequestParam(value = "type", required = false, defaultValue = "username") String type) throws Exception {
        // 公众号登录时，usrename为公众号的appid，password为授权时的code
        // 小程序登录时，usrename为小程序的appid，password为授权时的code
        // 手机号登录时，usrename为手机号，password为短信验证码的code

        // 公众号登录时，获取公众号粉丝信息
        if(BaseConstants.ACCOUNT_TYPE_MP.equalsIgnoreCase(type)) {
            // 从记录获取code登录对应的结果信息，因为前端会出现重复code登录，避免登录失败的临时解决方案
            if(BaseConstants.ACCOUNT_TYPE_MP.equalsIgnoreCase(type)) {
                String resultStr = redisUtils.get("uaaadmin:login_mp_" + password);
                if(!StringUtils.isEmpty(resultStr)) {
                    log.info("xiaoc>> 登录成功，重复的公众号code，获取缓存获取结果，result: {}", resultStr);
                    return ResultBodyUtils.ok(JsonUtils.str2Json(resultStr));
                }
            }

            BaseAccount baseAccount = getBaseAccountByMpCode(username, password);
            if(baseAccount == null) {
                log.info("xiaoc>> 公众号登录失败，username: {}, password: {}", username, password);
                return ResultBodyUtils.build(ResultEnum.AUTHORIZE_FAILED.getCode(), ResultEnum.AUTHORIZE_FAILED.getMessage());
            }

            username = baseAccount.getAccount();
        } else if(BaseConstants.ACCOUNT_TYPE_MINIPROGRAM.equalsIgnoreCase(type)) { // 小程序登录时，获取小程序粉丝信息
            BaseAccount baseAccount = getBaseAccountByMpminiCode(username, password, nickname, headimgurl);
            if(baseAccount == null) {
                log.info("xiaoc>> 小程序登录失败，username: {}, password: {}", username, password);
                return ResultBodyUtils.build(ResultEnum.AUTHORIZE_FAILED.getCode(), ResultEnum.AUTHORIZE_FAILED.getMessage());
            }

            username = baseAccount.getAccount();
        }

        // 门店员工手机号登录时，验证code与获取当前小程序登录信息
        long accountIdOld = 0;
        if(BaseConstants.ACCOUNT_TYPE_STAFF_PHONE.equalsIgnoreCase(type)) {
            String codeOld = redisUtils.get(UaaAdminConstants.REDIS_LOGIN_SMS_CODE_ + username);
            if(StringUtils.isEmpty(codeOld) || !codeOld.equalsIgnoreCase(password)) {
                return ResultBodyUtils.failed("验证码有误");
            }

            SecurityUserDetails securityUserDetails = SecurityHelper.getUser();
            if(securityUserDetails != null && BaseConstants.ACCOUNT_TYPE_MINIPROGRAM.equalsIgnoreCase(securityUserDetails.getAccountType()) && securityUserDetails.getAccountId() != null) {
                accountIdOld = securityUserDetails.getAccountId();
            } else {
                log.info("xiaoc>> 手机号登录，尝试小程序登录账号关联新员工登录账号失败，securityUserDetails: {}", JsonUtils.Obj2Str(securityUserDetails));
            }
        } else if(BaseConstants.ACCOUNT_TYPE_STORE_ACCOUNT.equalsIgnoreCase(type)) { // 门店登录
            SecurityUserDetails securityUserDetails = SecurityHelper.getUser();
            if(securityUserDetails != null && BaseConstants.ACCOUNT_TYPE_MINIPROGRAM.equalsIgnoreCase(securityUserDetails.getAccountType()) && securityUserDetails.getAccountId() != null) {
                accountIdOld = securityUserDetails.getAccountId();
            } else {
                log.info("xiaoc>> 门店登录，尝试小程序登录账号关联新门店登录账号失败，securityUserDetails: {}", JsonUtils.Obj2Str(securityUserDetails));
            }
        }

        // 登录
        OAuth2AccessToken result = getToken(username, password, type);
        // 门店员工手机号登录或门店登录时，添加登录账号关联
        if(BaseConstants.ACCOUNT_TYPE_STAFF_PHONE.equalsIgnoreCase(type) || BaseConstants.ACCOUNT_TYPE_STORE_ACCOUNT.equalsIgnoreCase(type)) {
            if(accountIdOld > 0) {
                mpminiAccountRefServiceClient.addMpminiAccountRef(accountIdOld, username);
                redisUtils.set(UaaAdminConstants.REDIS_LOGIN_ACCOUNT_REF_ + username, String.valueOf(accountIdOld), result.getExpiresIn());
            }
        } else if(BaseConstants.ACCOUNT_TYPE_MINIPROGRAM.equalsIgnoreCase(type)) { // 小程序登录时
            long accountId = DigitUtils.obj2Long(result.getAdditionalInformation().get(SecurityConstants.ACCOUNT_ID), 0);
            if(accountId > 0) {
                // 查看历史关联登录记录，自动登录
                ResultBody<MpminiAccountRef> retMpminiAccountRef = mpminiAccountRefServiceClient.getLastMpminiAccountRef(accountId);
                if (retMpminiAccountRef.getCode() == ResultEnum.OK.getCode() && retMpminiAccountRef.getData() != null) {
                    MpminiAccountRef mpminiAccountRef = retMpminiAccountRef.getData();
                    //把参数存到session中
                    Map<String,String> attributeMap = new HashMap<>();
                    String newUsername = mpminiAccountRef.getAccountUsernameRef();
                    String newPassword = RandomValueUtils.random(6);
                    attributeMap.put("username",newUsername);
                    attributeMap.put("password",newPassword);
                    attributeMap.put("type",BaseConstants.ACCOUNT_TYPE_SYSTEM_AUTO);
                    WebUtils.setAttributeMap(attributeMap);
                    result = getToken(newUsername, newPassword, BaseConstants.ACCOUNT_TYPE_SYSTEM_AUTO);

                    redisUtils.set(UaaAdminConstants.REDIS_LOGIN_ACCOUNT_REF_ + newUsername, String.valueOf(accountId), result.getExpiresIn());
                }
            }
        }

        // 微信公众号登录时，记录code登录对应的结果信息，因为前端会出现重复code登录，避免登录失败的临时解决方案
        if(BaseConstants.ACCOUNT_TYPE_MP.equalsIgnoreCase(type)) {
            redisUtils.set("uaaadmin:login_mp_" + password, JsonUtils.Obj2Str(result), 10 * 60);
        }

        log.info("xiaoc>> 登录成功，result: {}", JsonUtils.Obj2Str(result));
        return ResultBodyUtils.ok(result);
    }

    /**
     * 获取登录验证码
     * @param phone
     * @return
     * @throws Exception
     */
    @PostMapping("/login/smscode")
    public Object getSmscode(
            @RequestParam(value = "phone") String phone) throws Exception {
        // 获取历史发送次数
        String sendTimesStr = redisUtils.get(UaaAdminConstants.REDIS_LOGIN_SMS_CODE_TIMES_ + phone);
        int sendTimes = DigitUtils.str2Int(sendTimesStr, 0);

        if(sendTimes >= 1) {
            log.info("xiaoc>> 请求发送验证码失败，操作过于频繁，sendTimes: {}", sendTimes);
            return ResultBodyUtils.failed("操作过于频繁，请稍后再试");
        }

        String code = RandomValueUtils.randomNumeric(6);
        ResultBody<String> retSendCode = aliSmsServiceClient.sendCode(phone, code, SmsTypeEnum.LOGIN_PHONE_CODE);
        if(retSendCode.getCode() != ResultEnum.OK.getCode()) {
            return retSendCode;
        }

        // 将验证码保存redis
        redisUtils.set(UaaAdminConstants.REDIS_LOGIN_SMS_CODE_ + phone, code, 10 * 60);
        redisUtils.set(UaaAdminConstants.REDIS_LOGIN_SMS_CODE_TIMES_ + phone, String.valueOf(sendTimes + 1), 60);

        log.info("xiaoc>> 已发送登录验证码，code: {}", code);
        return ResultBodyUtils.ok("发送成功");
    }

    /**
     * 退出移除令牌
     * @param Authorization 访问令牌
     */
    @PostMapping("/logout/token")
    public ResultBody removeToken(@RequestParam(value = "Authorization", required = false) String Authorization,
                                  @RequestParam(value = "username", required = false) String username) {
        // 指定的用户登出
        if(!StringUtils.isEmpty(username)) {
            Collection<OAuth2AccessToken> collection = tokenStore.findTokensByClientIdAndUserName(commonProperties.getClientId(), username);
            if(collection != null) {
                for(OAuth2AccessToken item : collection) {
                    try {
                        OAuth2AccessToken oAuth2AccessToken = tokenStore.readAccessToken(item.getValue());
                        log.info("指定用户登出 oAuth2AccessToken: {}", JsonUtils.Obj2Str(oAuth2AccessToken));
                        tokenStore.removeAccessToken(item);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
                redisUtils.del(UaaAdminConstants.REDIS_LOGIN_ACCOUNT_REF_ + username);
            }
        } else if(!StringUtils.isEmpty(Authorization)) { // 自己登出
            //校验是否是token == Bearer 22ce2c1e-bfa7-43bd-8bde-d497cc7d5389
            if (!Authorization.contains("Bearer")) {
                return ResultBodyUtils.failed("无效的token");
            }
            String token = Authorization.substring(7);
            SecurityUserDetails securityUserDetails = SecurityHelper.getUser();
            if (BaseConstants.ACCOUNT_DOMAIN_USER.equalsIgnoreCase(securityUserDetails.getDomain())) {
                //如果是用户删除关联账户
                String account = securityUserDetails.getUsername();
                long accountId = DigitUtils.obj2Long(redisUtils.get(UaaAdminConstants.REDIS_LOGIN_ACCOUNT_REF_ + account), -1);
                if (StringUtils.isNotEmpty(account) && accountId > 0) {
                    mpminiAccountRefServiceClient.removeMpminiAccountRef(accountId, account);
                }
                redisUtils.del(UaaAdminConstants.REDIS_LOGIN_ACCOUNT_REF_ + account);
            }
            tokenStore.removeAccessToken(tokenStore.readAccessToken(token));
        }

        return ResultBodyUtils.ok();
    }

    /**
     * 第三方登录获取token
     * @param userName
     * @param password
     * @param type username:系统用户名、email：邮箱、mobile：手机号、qq：QQ号、mpweixin：微信号、weibo：微博、mp：微信公众号、miniprogram：微信小程序
     * @return
     */
    public OAuth2AccessToken getToken(String userName, String password, String type) throws Exception {
        OAuth2ClientDetails clientDetails =  oAuth2ClientProperties.getOauth2().get("admin");
        // 使用oauth2密码模式登录.
        Map<String, String> postParameters = Maps.newHashMap();
        postParameters.put("username", userName);
        postParameters.put("password", password);
        postParameters.put("client_id", clientDetails.getClientId());
        postParameters.put("client_secret", clientDetails.getClientSecret());
        postParameters.put("grant_type", "password");
        // 添加参数区分,第三方登录
        postParameters.put("login_type", type);
        log.info("xiaoc>> UAA请求参数：{}", JsonUtils.Obj2Str(postParameters));
        return  SecurityHelper.createAccessToken(endpoints, postParameters);
    }

    /**
     * 微信网页授权获取粉丝信息
     * @param appid
     * @param code
     * @return
     */
    private BaseAccount getBaseAccountByMpCode(String appid, String code) {
        ResultBody<AccessToken> retAccessToken = mpopenApiServiceClient.getAccessToken(appid, code);
        if(retAccessToken.getCode() == ResultEnum.OK.getCode()) {
            if(retAccessToken.getData() != null && !StringUtils.isEmpty(retAccessToken.getData().getOpenid())) {
                String openid = retAccessToken.getData().getOpenid();
                ResultBody<BaseAccount> retBaseAccount = baseUserServiceClient.getBaseAccount(appid + ":" + openid, BaseConstants.ACCOUNT_TYPE_MP, BaseConstants.ACCOUNT_DOMAIN_MP);
                if(retBaseAccount.getCode() == ResultEnum.OK.getCode()) {
                    return retBaseAccount.getData();
                }

                // 网页授权获取公众号粉丝信息
                String access_token = retAccessToken.getData().getAccess_token();
                ResultBody<MpweixinUser> retMpweixinUser = mpweixinApiServiceClient.getMpweixinUser(appid, openid, access_token);
                if(retMpweixinUser.getCode() == ResultEnum.OK.getCode() && retMpweixinUser.getData() != null) {
                    MpweixinUser mpweixinUser = retMpweixinUser.getData();
                    // 添加用户信息
                    ResultBody<BaseAccount> retAddBaseAccount = baseUserServiceClient.addBaseAccount(appid + ":" + openid, RandomValueUtils.random(16),
                            BaseConstants.ACCOUNT_TYPE_MP, BaseConstants.ACCOUNT_DOMAIN_MP, "公众号粉丝",
                            mpweixinUser.getNickname(), mpweixinUser.getHeadimgurl()
                    );

                    if(retAddBaseAccount.getCode() == ResultEnum.OK.getCode()) {
                        return retAddBaseAccount.getData();
                    }
                } else {
                    log.error("公众号粉丝授权，获取粉丝信息失败，retMpweixinUser: {}", JsonUtils.Obj2Str(retMpweixinUser));
                }
            } else {
                log.error("公众号粉丝授权，获取粉丝openid失败，retAccessToken.data: {}", JsonUtils.Obj2Str(retAccessToken.getData()));
            }
        } else {
            log.error("公众号粉丝授权，获取粉丝openid失败，retAccessToken: {}", JsonUtils.Obj2Str(retAccessToken));
        }

        return null;
    }

    /**
     * 小程序授权获取粉丝信息
     * @param appid
     * @param code
     * @return
     */
    private BaseAccount getBaseAccountByMpminiCode(String appid, String code,String nickname, String headimgurl) {
        ResultBody<MpminiUser> retMpminiUser = mpminiApiServiceClient.getMpminiUser(nickname,headimgurl,code);
        if (retMpminiUser.getCode() == ResultEnum.OK.getCode()) {
            MpminiUser mpminiUser = retMpminiUser.getData();
            if (mpminiUser != null && !StringUtils.isEmpty(mpminiUser.getOpenId())) {
                String openid = mpminiUser.getOpenId();
                ResultBody<BaseAccount> retBaseAccount = baseUserServiceClient.getBaseAccount(appid + ":" + openid, BaseConstants.ACCOUNT_TYPE_MINIPROGRAM, BaseConstants.ACCOUNT_DOMAIN_MPMINI);
                if(retBaseAccount.getCode() == ResultEnum.OK.getCode()) {
                    return retBaseAccount.getData();
                }

                // 添加用户信息
                ResultBody<BaseAccount> retAddBaseAccount = baseUserServiceClient.addBaseAccount(appid + ":" + openid, RandomValueUtils.random(16),
                        BaseConstants.ACCOUNT_TYPE_MINIPROGRAM, BaseConstants.ACCOUNT_DOMAIN_MPMINI, "小程序粉丝",
                        nickname, headimgurl
                );

                if(retAddBaseAccount.getCode() == ResultEnum.OK.getCode()) {
                    return retAddBaseAccount.getData();
                }
            } else {
                log.error("小程序粉丝授权，获取粉丝信息失败，retMpminiUser.data: {}", JsonUtils.Obj2Str(retMpminiUser.getData()));
            }
        } else {
            log.error("小程序粉丝授权，获取粉丝信息失败，retMpminiUser: {}", JsonUtils.Obj2Str(retMpminiUser));
        }

        return null;
    }

}
